package com.xf.filter;

import org.apache.dubbo.common.constants.CommonConstants;
import org.apache.dubbo.common.extension.Activate;
import org.apache.dubbo.rpc.*;
import org.springframework.util.CollectionUtils;

import java.util.*;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * @ClassName DubboInvokeFilter
 * @Deseription 自定义拦截器
 * @Author fx
 * @Date 2021/4/27 0027 22:09
 * @Version 1.0
 **/
@Activate(group = {CommonConstants.CONSUMER})
public class DubboInvokeFilter implements Filter {

    private static int coreSize = Runtime.getRuntime().availableProcessors();
    private static ScheduledExecutorService scheduledPool = Executors.newScheduledThreadPool(coreSize);

    private static List<MethodTime> methodTimeList = new ArrayList<>(2000);

    @Override
    public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
        // 创建保存方法执行时间记录对象
        MethodTime methodTime = new MethodTime();
        // 方法执行开始时间
        long startTime = System.currentTimeMillis();
        // 获取方法名称
        String methodName = invocation.getMethodName();
        try {
            //执行方法
            return invoker.invoke(invocation);
        } finally {
            // 方法执行完时间
            long endTime = System.currentTimeMillis();
            // 方法执行完所消耗时间
            long consumingTime = endTime - startTime;
            // 添加方法名
            methodTime.setMethodName(methodName);
            // 添加方法执行开始时间
            methodTime.setMethodStartTimeMill(startTime);
            // 添加方法执行完时间
            methodTime.setMethodEndTimeMill(endTime);
            // 添加方法执行完所消耗时间
            methodTime.setMethodDuration(consumingTime);
            // 将方法记录对象存入集合
            methodTimeList.add(methodTime);
        }
    }

    static {
        // 加载任务统计
        statisticalTask();
    }

    private static void statisticalTask() {
        // 设置每隔5秒执行t统计TP90、TP99耗时记录情况
        scheduledPool.scheduleWithFixedDelay(DubboInvokeFilter::doStatisticalMethodTpInfo, 0, 5, TimeUnit.SECONDS);
    }


    /**
     统计TP90、TP99耗时记录情况
     */
    private static void doStatisticalMethodTpInfo() {
        //1.获取当前时间戳
        long currentTimeMillis = System.currentTimeMillis();
        //1.1 前一分钟的时间戳
        long oneMinuteTimeMill = currentTimeMillis - 60000;
        //2.获取前一分钟的方法时间集合
        if (methodTimeList.size() == 0) {
            return;
        }
        // 过滤一分钟内的数据
        List<MethodTime> timeList = methodTimeList.stream().
                filter(i -> i.getMethodStartTimeMill() >= oneMinuteTimeMill && i.getMethodEndTimeMill() <= currentTimeMillis).
                collect(Collectors.toList());
        if (timeList.size() == 0) {
            return;
        }
        //3.根据方法名分组
        Map<String, List<MethodTime>> map = timeList.stream().collect(Collectors.groupingBy(MethodTime::getMethodName));
        //3.1获取方法A的信息
        List<MethodTime> sayHelloAList = map.get("sayHelloA");
        if (!CollectionUtils.isEmpty(sayHelloAList)) {
            // 按照方法执行耗时时间进行排序
            List<MethodTime> sortSayHelloAList = sayHelloAList.stream().sorted(Comparator.comparing(MethodTime::getMethodDuration)).collect(Collectors.toList());
            getTpInfo(0.9, sortSayHelloAList);
            getTpInfo(0.99, sortSayHelloAList);
        }
        //3.2获取方法B的信息
        List<MethodTime> sayHelloBList = map.get("sayHelloB");
        if (!CollectionUtils.isEmpty(sayHelloBList)) {
            List<MethodTime> sortSayHelloBList = sayHelloBList.stream().sorted(Comparator.comparing(MethodTime::getMethodDuration)).collect(Collectors.toList());
            getTpInfo(0.9, sortSayHelloBList);
            getTpInfo(0.99, sortSayHelloBList);
        }
        //3.3获取方法C的信息
        List<MethodTime> sayHelloCList = map.get("sayHelloC");
        if (!CollectionUtils.isEmpty(sayHelloCList)) {
            List<MethodTime> sortSayHelloCList = sayHelloCList.stream().sorted(Comparator.comparing(MethodTime::getMethodDuration)).collect(Collectors.toList());
            getTpInfo(0.9, sortSayHelloCList);
            getTpInfo(0.99, sortSayHelloCList);
        }
    }


    private static void getTpInfo(double ratio, List<MethodTime> methodTimes) {
        // 当前集合的数量
        int size = methodTimes.size();
        // 根据类型获取集合需要获取的下标
        int ceil = (int) Math.ceil(size * ratio);
        // 根据下标获取指定的数据
        MethodTime methodTime = methodTimes.get(ceil - 1);
        System.out.println("方法：" + methodTime.getMethodName() + "的TP" + (int) (ratio * 100)
                + "为=====================>>" + methodTime.getMethodDuration() + "毫秒");
    }

}
